﻿using DotNetCommon.Extensions;
using System;
using System.Buffers.Text;
using System.Buffers;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text;

namespace DotNetCommon.Serialize
{
    #region DateOnly & TimeOnly
    /// <summary>
    /// 专为 .net6 提供
    /// </summary>
    internal sealed class JsonConverterDateOnly : JsonConverter<DateOnly>
    {
        public string Format { get; set; }
        public JsonConverterDateOnly()
        {
            this.Format = "yyyy-MM-dd";
        }
        public JsonConverterDateOnly(string format)
        {
            Format = format ?? throw new ArgumentNullException(nameof(format));
        }
        public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            return DateOnly.Parse(reader.GetString());
        }

        public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options)
        {
            writer.WriteStringValue(value.ToString(Format));
        }
    }

    /// <summary>
    /// 专为 .net6 提供
    /// </summary>
    internal sealed class JsonConverterTimeOnly : JsonConverter<TimeOnly>
    {
        public string Format { get; set; }
        public JsonConverterTimeOnly()
        {
            this.Format = "HH:mm:ss.fffffff";
        }
        public JsonConverterTimeOnly(string format)
        {
            Format = format ?? throw new ArgumentNullException(nameof(format));
        }
        public override TimeOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            return TimeOnly.Parse(reader.GetString());
        }

        public override void Write(Utf8JsonWriter writer, TimeOnly value, JsonSerializerOptions options)
        {
            writer.WriteStringValue(value.ToString(Format));

        }
    }
    #endregion

    #region Datetime & DateTimeOffset
    /// <summary>
    /// DateTime转换器
    /// </summary>
    internal class JsonConverterDatetime : JsonConverter<DateTime>
    {
        /// <summary>
        /// 如: "yyyy-MM-dd HH:mm:ss",默认: null
        /// </summary>
        public string Format { get; set; }
        /// <summary>
        /// 使用默认格式,同: <c>System.Text.Json.JsonSerializer.Serialize(obj)</c>
        /// </summary>
        public JsonConverterDatetime() { }
        /// <summary>
        /// 指定格式化字符串
        /// </summary>
        public JsonConverterDatetime(string format)
        {
            Format = format;
        }
        /// <summary>
        /// 自定义反序列化方法
        /// </summary>
        public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if (!reader.TryGetDateTime(out DateTime value))
            {
                value = DateTime.Parse(reader.GetString()!);
            }
            return value;
        }

        /// <summary>
        /// 自定义序列化方法
        /// </summary>
        public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
        {
            if (Format.IsNotNullOrEmptyOrWhiteSpace())
            {
                writer.WriteStringValue(value.ToString(Format));
            }
            else
            {
                Span<byte> utf8Date = new byte[50];
                bool result = Utf8Formatter.TryFormat(value, utf8Date, out int len, new StandardFormat('O'));
                if (result) writer.WriteStringValue(utf8Date.Slice(0, len));
                else writer.WriteStringValue(value.ToString(Format));
            }
        }
    }

    /// <summary>
    /// DateTimeOffset转换器
    /// </summary>
    internal class JsonConverterDateTimeOffset : JsonConverter<DateTimeOffset>
    {
        /// <summary>
        /// 如: "yyyy-MM-dd HH:mm:ss",默认: null
        /// </summary>
        public string Format { get; set; }
        /// <summary>
        /// 使用默认格式,同: <c>System.Text.Json.JsonSerializer.Serialize(obj)</c>
        /// </summary>
        public JsonConverterDateTimeOffset() { }
        /// <summary>
        /// 指定格式化字符串
        /// </summary>
        /// <param name="format"></param>
        public JsonConverterDateTimeOffset(string format)
        {
            Format = format;
        }
        /// <summary>
        /// 自定义反序列化方法
        /// </summary>
        public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if (!reader.TryGetDateTimeOffset(out DateTimeOffset value))
            {
                value = DateTimeOffset.Parse(reader.GetString()!);
            }
            return value;
        }

        /// <summary>
        /// 自定义序列化方法
        /// </summary>
        public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
        {
            if (Format.IsNotNullOrEmptyOrWhiteSpace())
            {
                writer.WriteStringValue(value.ToString(Format));
            }
            else
            {
                Span<byte> utf8Date = new byte[50];
                bool result = Utf8Formatter.TryFormat(value, utf8Date, out int len, new StandardFormat('O'));
                if (result) writer.WriteStringValue(utf8Date.Slice(0, len));
                else writer.WriteStringValue(value.ToString(Format));
            }
        }
    }
    #endregion

    /// <summary>
    /// string转换器,解决问题: <br />
    /// 123 => "123"<br/>
    /// true => "true"
    /// </summary>
    internal class JsonConverterString : JsonConverter<string>
    {
        /// <summary>
        /// 空参构造函数
        /// </summary>
        public JsonConverterString() { }
        /// <summary>
        /// 自定义反序列化方法
        /// </summary>
        public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            switch (reader.TokenType)
            {
                case JsonTokenType.None:
                case JsonTokenType.Null:
                    return null;
                case JsonTokenType.Number:
                    return reader.GetDouble().ToString();
                case JsonTokenType.True:
                    return "true";
                case JsonTokenType.False:
                    return "false";
                default:
                    return reader.GetString();
            }
        }

        /// <summary>
        /// 自定义序列化方法
        /// </summary>
        public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
        {
            writer.WriteStringValue(value);
        }
    }

    internal class JsonConverterBool : JsonConverter<bool>
    {
        public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            switch (reader.TokenType)
            {
                case JsonTokenType.None:
                case JsonTokenType.Null:
                    throw new Exception($"无法将 null 反序列化为 bool!");
                case JsonTokenType.Number:
                    var d = reader.GetDouble();
                    return !(d == 0);
                case JsonTokenType.String:
                    var str = reader.GetString();
                    if (string.Equals(str, "true", StringComparison.OrdinalIgnoreCase)) return true;
                    else if (string.Equals(str, "false", StringComparison.OrdinalIgnoreCase)) return false;
                    else throw new Exception($"无法将非 \"true\"或\"false\" 的字符串 转为 bool!");
                case JsonTokenType.True: return true;
                case JsonTokenType.False: return false;
                default: throw new Exception($"无法将 {reader.TokenType} 反序列化为 bool!");
            }
        }

        public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)
        {
            writer.WriteBooleanValue(value);
        }
    }
    internal class NullAbleConverter : JsonConverterFactory
    {
        private static ConcurrentDictionary<Type, JsonConverter> _cache = new();
        public override bool CanConvert(Type typeToConvert)
        {
            return typeToConvert.IsNullable();
        }

        public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
        {
            return _cache.GetOrAdd(typeToConvert, () =>
            {
                return (JsonConverter)Activator.CreateInstance(typeof(NullAbleConverter<>).MakeGenericType(typeToConvert));
            });
        }
    }

    internal class NullAbleConverter<T> : JsonConverter<T>
    {
        public NullAbleConverter()
        {
            s_UnderlyingType = Nullable.GetUnderlyingType(typeof(T));
        }
        private Type s_UnderlyingType = null;
        public override bool CanConvert(Type typeToConvert)
        {
            return typeToConvert.IsNullable();
        }
        public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            switch (reader.TokenType)
            {
                case JsonTokenType.Null:
                case JsonTokenType.None:
                    return default;
                case JsonTokenType.String:
                    var str = reader.GetString();
                    if (str.IsNullOrEmptyOrWhiteSpace()) return default;
                    return (T)JsonSerializer.Deserialize(ref reader, s_UnderlyingType, options);
                case JsonTokenType.StartArray:
                    {
                        if (s_UnderlyingType.IsEnum)
                        {
                            //将 [] 转为 EnumFlag? 的时候转为 null
                            return (T)JsonEnumConverter.ConvertFromReaderWhenArray(ref reader, s_UnderlyingType, typeToConvert);
                        }
                        return (T)JsonSerializer.Deserialize(ref reader, s_UnderlyingType, options);
                    }
                default:
                    return (T)JsonSerializer.Deserialize(ref reader, s_UnderlyingType, options);
            }
        }

        public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
        {
            if (value == null)
            {
                writer.WriteStringValue("null");
            }
            else
            {
                JsonSerializer.Serialize(writer, value, value.GetType(), options);
            }
        }
    }

    internal class JsonEnumConverter : JsonConverterFactory
    {
        internal static object ConvertFromReaderWhenArray(ref Utf8JsonReader reader, Type typeToConvert, Type oldType)
        {
            var sb = new StringBuilder();
            sb.Append('[');
            var idx = 0;
            while (reader.Read())
            {
                if (reader.TokenType == JsonTokenType.EndArray)
                {
                    sb.Append(']');
                    break;
                }
                else if (reader.TokenType == JsonTokenType.String)
                {
                    idx++;
                    if (idx > 1) sb.Append(',');
                    sb.Append('"').Append(reader.GetString()).Append('"');
                }
                else if (reader.TokenType == JsonTokenType.Number)
                {
                    idx++;
                    if (idx > 1) sb.Append(',');
                    sb.Append(reader.GetInt32());
                }
                else throw new Exception($"无法将数组转成枚举({typeToConvert.GetClassFullName()}),除非是简单数组(如:[1,2],[\"close\",\"open\"])!");
            }
            return ObjectExtensions.ConvertToEnum(typeToConvert, oldType, sb.ToString());
        }
        public JsonEnumConverter(bool enum2String)
        {
            this.enum2String = enum2String;
        }
        private static ConcurrentDictionary<Type, JsonConverter> _cache2String = new();
        private static ConcurrentDictionary<Type, JsonConverter> _cache2Numer = new();
        private readonly bool enum2String;

        public sealed override bool CanConvert(Type typeToConvert)
        {
            return typeToConvert.IsEnum;
        }

        public sealed override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
        {
            var _cache = enum2String ? _cache2String : _cache2Numer;
            return _cache.GetOrAdd(typeToConvert, () =>
            {
                var ctor = typeof(EnumConverter<>).MakeGenericType(typeToConvert).GetConstructor(new Type[] { typeof(bool) });
                return (JsonConverter)ctor.Invoke(new object[] { enum2String });
            });
        }
    }

    internal sealed class EnumConverter<T> : JsonConverter<T> where T : struct, Enum
    {
        public EnumConverter(bool enum2String)
        {
            this.enum2String = enum2String;
        }
        private static readonly TypeCode s_enumTypeCode = Type.GetTypeCode(typeof(T));
        private readonly bool enum2String;

        public override bool CanConvert(Type type)
        {
            return type.IsEnum;
        }

        public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            switch (reader.TokenType)
            {
                case JsonTokenType.String:
                    {
                        var str = reader.GetString();
                        if (Enum.TryParse(typeof(T), str, true, out var ret)) return (T)ret;
                        return (T)ObjectExtensions.ConvertToEnum(typeToConvert, typeToConvert, str);
                    }
                case JsonTokenType.Number:
                    {
                        switch (s_enumTypeCode)
                        {
                            case TypeCode.Int32:
                                {
                                    if (reader.TryGetInt32(out var value8))
                                    {
                                        return Unsafe.As<int, T>(ref value8);
                                    }
                                    break;
                                }
                            case TypeCode.UInt32:
                                {
                                    if (reader.TryGetUInt32(out var value4))
                                    {
                                        return Unsafe.As<uint, T>(ref value4);
                                    }
                                    break;
                                }
                            case TypeCode.UInt64:
                                {
                                    if (reader.TryGetUInt64(out var value6))
                                    {
                                        return Unsafe.As<ulong, T>(ref value6);
                                    }
                                    break;
                                }
                            case TypeCode.Int64:
                                {
                                    if (reader.TryGetInt64(out var value2))
                                    {
                                        return Unsafe.As<long, T>(ref value2);
                                    }
                                    break;
                                }
                            case TypeCode.SByte:
                                {
                                    if (reader.TryGetSByte(out var value7))
                                    {
                                        return Unsafe.As<sbyte, T>(ref value7);
                                    }
                                    break;
                                }
                            case TypeCode.Byte:
                                {
                                    if (reader.TryGetByte(out var value5))
                                    {
                                        return Unsafe.As<byte, T>(ref value5);
                                    }
                                    break;
                                }
                            case TypeCode.Int16:
                                {
                                    if (reader.TryGetInt16(out var value3))
                                    {
                                        return Unsafe.As<short, T>(ref value3);
                                    }
                                    break;
                                }
                            case TypeCode.UInt16:
                                {
                                    if (reader.TryGetUInt16(out var value))
                                    {
                                        return Unsafe.As<ushort, T>(ref value);
                                    }
                                    break;
                                }
                        }
                        throw new Exception($"无法从 {JsonTokenType.Number} 转为枚举({typeToConvert.GetClassFullName()})!");
                    }
                case JsonTokenType.StartArray:
                    return (T)JsonEnumConverter.ConvertFromReaderWhenArray(ref reader, typeToConvert, typeToConvert);
                default:
                    throw new Exception($"无法从 {reader.TokenType} 转为枚举({typeToConvert.GetClassFullName()})!");
            }
        }

        public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
        {
            if (enum2String)
            {
                writer.WriteStringValue(value.ToString());
            }
            else
            {
                switch (s_enumTypeCode)
                {
                    case TypeCode.Int32:
                        writer.WriteNumberValue(Unsafe.As<T, int>(ref value));
                        break;
                    case TypeCode.UInt32:
                        writer.WriteNumberValue(Unsafe.As<T, uint>(ref value));
                        break;
                    case TypeCode.UInt64:
                        writer.WriteNumberValue(Unsafe.As<T, ulong>(ref value));
                        break;
                    case TypeCode.Int64:
                        writer.WriteNumberValue(Unsafe.As<T, long>(ref value));
                        break;
                    case TypeCode.Int16:
                        writer.WriteNumberValue(Unsafe.As<T, short>(ref value));
                        break;
                    case TypeCode.UInt16:
                        writer.WriteNumberValue(Unsafe.As<T, ushort>(ref value));
                        break;
                    case TypeCode.Byte:
                        writer.WriteNumberValue(Unsafe.As<T, byte>(ref value));
                        break;
                    case TypeCode.SByte:
                        writer.WriteNumberValue(Unsafe.As<T, sbyte>(ref value));
                        break;
                    default:
                        throw new Exception($"无法将 {s_enumTypeCode} 序列化!");
                }
            }
        }

        public override T ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            switch (reader.TokenType)
            {
                case JsonTokenType.String:
                    {
                        var str = reader.GetString();
                        if (Enum.TryParse(typeof(T), str, true, out var ret)) return (T)ret;
                        return (T)ObjectExtensions.ConvertToEnum(typeToConvert, typeToConvert, str);
                    }
                case JsonTokenType.Number:
                    switch (s_enumTypeCode)
                    {
                        case TypeCode.Int32:
                            {
                                if (reader.TryGetInt32(out var value8))
                                {
                                    return Unsafe.As<int, T>(ref value8);
                                }
                                break;
                            }
                        case TypeCode.UInt32:
                            {
                                if (reader.TryGetUInt32(out var value4))
                                {
                                    return Unsafe.As<uint, T>(ref value4);
                                }
                                break;
                            }
                        case TypeCode.UInt64:
                            {
                                if (reader.TryGetUInt64(out var value6))
                                {
                                    return Unsafe.As<ulong, T>(ref value6);
                                }
                                break;
                            }
                        case TypeCode.Int64:
                            {
                                if (reader.TryGetInt64(out var value2))
                                {
                                    return Unsafe.As<long, T>(ref value2);
                                }
                                break;
                            }
                        case TypeCode.SByte:
                            {
                                if (reader.TryGetSByte(out var value7))
                                {
                                    return Unsafe.As<sbyte, T>(ref value7);
                                }
                                break;
                            }
                        case TypeCode.Byte:
                            {
                                if (reader.TryGetByte(out var value5))
                                {
                                    return Unsafe.As<byte, T>(ref value5);
                                }
                                break;
                            }
                        case TypeCode.Int16:
                            {
                                if (reader.TryGetInt16(out var value3))
                                {
                                    return Unsafe.As<short, T>(ref value3);
                                }
                                break;
                            }
                        case TypeCode.UInt16:
                            {
                                if (reader.TryGetUInt16(out var value))
                                {
                                    return Unsafe.As<ushort, T>(ref value);
                                }
                                break;
                            }
                    }
                    throw new Exception($"无法从 {JsonTokenType.Number} 转为枚举({typeToConvert.GetClassFullName()})!");
                case JsonTokenType.PropertyName:
                    {
                        var str = reader.GetString();
                        if (Enum.TryParse(typeof(T), str, true, out var ret)) return (T)ret;
                        return (T)ObjectExtensions.ConvertToEnum(typeToConvert, typeToConvert, str);
                    }
                case JsonTokenType.StartArray:
                    return (T)JsonEnumConverter.ConvertFromReaderWhenArray(ref reader, typeToConvert, typeToConvert);
                default:
                    throw new Exception($"无法从 {reader.TokenType} 转为枚举({typeToConvert.GetClassFullName()})!");
            }
        }

        public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
        {
            writer.WritePropertyName(value.ToString());
        }
    }
}
